/* disp.c : dispatcher
 	Roles: Process system calls, schedule next process, calls context switcher
*/

#include <kernel.h>
#include <i386.h>
#include <stdarg.h>


/* Function definitions: */
void printpcbTable();
void printReadyQueue();
void printQueue(queueElem* queue);
void disp_show();

/* Create initial list and queue pointers */
pcb* rootPCB;
pcb* idleprocPCB;
pcb* pcbTable = NULL;
queueElem* sendQueue = NULL;
queueElem* recvQueue = NULL;
queueElem* readyQueue = NULL;

/* 	Takes a process pointer and adds to a new queue node end of the queue. Returns TRUE if successful. */
int pushQueue( pcb* process, queueElem * * queue, process_state inQueueState){
	if(process == NULL){
		/* we were told to add nothing to the table. Abort safely. */
		return FALSE;
	}
	
	/* Create a new queue element for this process. */
	queueElem * newProcElem	= kmalloc(sizeof(queueElem));
	if (newProcElem == NULL) {
		kprintf("Failed to add process %d to queue, out of memory!\n", process->pid);
		return FALSE;
	}
	
	/* Fill in its fields */
	newProcElem -> processPointer 			= process;
	newProcElem -> next						= NULL;
	newProcElem -> processPointer -> state 	= inQueueState;
	
	/* Update the end elem to point to this one. Find the last queue elem. */
	if (*queue == NULL){
		/* The queue is empty. */
		verbose("Creating new queue.\n");
		*queue = newProcElem;
	}
	else {
		queueElem * indexPQE = *queue;
		while (indexPQE->next != NULL){
			indexPQE = (queueElem*) indexPQE->next;
		}
		indexPQE->next = (struct queueElem*) newProcElem;
	}
	
	return TRUE;
}

/* 	returns a pointer to the next process in the queue, and removes it from the front of the queue.
 	If the queue is empty, returns NULL. 
	Can also specify a specific PID of a process to be removed, regardless of its position. */
pcb* popQueue(queueElem * * queue, int popPID, process_state newState){
	if (*queue == NULL || queue == NULL){
		if (DISP_UNIT_TEST) kprintf("popQueue: Queue is empty!\n");
		return NULL;
	}
	
	pcb* process = NULL;

	if (popPID != NULL){
		/* Caller selected a specific process to remove. */
		if (DISP_UNIT_TEST) kprintf("popQueue: Removing pid %d from queue.\n", popPID);
		queueElem * indexElem 	= *queue;
		queueElem * prevElem 	= NULL;
		
		while (indexElem != NULL){
			/* Check if this element is the proc we want */
			if (indexElem->processPointer->pid == popPID){
				if (DISP_UNIT_TEST) kprintf("popQueue: Process %d found in queue.\n", popPID);
				process = indexElem->processPointer;
				/* Remove the element */
				if (prevElem == NULL) {
					*queue = (queueElem*) indexElem->next;
				}
				else {
					prevElem->next = indexElem->next; /* could be NULL, that's fine. */
				}
				kfree(indexElem);
				/* Set the process to the new state */
				process -> state = newState;
				return process;
			}
			else if (indexElem->next != NULL){
				prevElem  = indexElem; 
				indexElem = (queueElem*) indexElem->next;
			}
		}
		/* Didn't find it. Return the error code */
		if (DISP_UNIT_TEST) kprintf("popQueue: Process %d not found in queue.\n", popPID);
		return NULL;
	}
	else {	
		/* Pop the front element */
		queueElem * front = *queue;
		/* Keep the process, since it will be gone once we free the element. */
		process = front->processPointer;
		/* Make the second-in-line the new front. */
		*queue = (queueElem*) front->next;
		/* kfree() the popped element. Without this step, we'll have a massive memory leak as the disp runs. */
		kfree(front);
		if (DISP_UNIT_TEST && *queue == NULL) kprintf("popQueue: Queue destroyed.\n");
	}
	if (process == NULL){
		/* Something bad happened */
		verbose("popQueue error: NULL process!\n");
		return NULL;
	}
	/* Set the process to the new state */
	process -> state = newState;
	return process;
}

pcb* findInQueue(queueElem * queue, int findpid){
	if ((queue == NULL) || (findpid < 1)){
		if (DISP_UNIT_TEST) kprintf("findInQueue: Queue is empty, or illegal pid requested!\n");
		return NULL;
	}
	queueElem * indexElem 	= queue;
	queueElem * prevElem 	= NULL;
	
	while (indexElem != NULL){
		/* Check if this element is the proc we want */
		if (indexElem->processPointer->pid == findpid){
			if (DISP_UNIT_TEST) kprintf("findInQueue: Process %d found in queue.\n", findpid);
			return indexElem->processPointer;
		}
		else if (indexElem->next != NULL){
			prevElem  = indexElem; 
			indexElem = (queueElem*) indexElem->next;
		}
	}
	/* Didn't find it. Return the error code */
	if (DISP_UNIT_TEST) kprintf("findInQueue: Process %d not found in queue.\n", findpid);
	return NULL;
}

/* Wrapper for pushQueue that uses the readyQueue */
int ready( pcb* process){
	int retValue = pushQueue(process, &readyQueue, READY);
	if (DISP_UNIT_TEST) 
		if (retValue == TRUE) 
			kprintf("Process %d placed on ready queue.\n", process->pid);
	return retValue;
}

/* Wrapper for pushQueue that uses the blockQueue for a given shared resource*/
int block( pcb* process, queueElem* blockQueue){	
	int retValue = pushQueue(process, blockQueue, BLOCKED);
	if (DISP_UNIT_TEST) 
		if (retValue == TRUE) 
			kprintf("Process %d placed on blocked queue.\n", process->pid);
	return retValue;
}

/* Wrapper for popQueue that uses a blockQueue. If popPID is NULL, pop the front of the queue. */
pcb* unblock(queueElem* blockQueue, int popPID){
	/* moves the process at the front of the blocked queue to the end of the ready queue.
	 	If the blocked queue is empty, does nothing. */
	return popQueue(blockQueue, popPID, READY);
}

/* Wrapper for popQueue that uses the readyQueue */
pcb* next(){
	/* Returns the process that is at the front of the ready queue */
	pcb* process = popQueue(&readyQueue, NULL, RUNNING);
	if (DISP_UNIT_TEST && process != NULL)
		kprintf("Popped process %d from the front of the ready queue.\n", process->pid);
	return process;
}

void initDispatcher(){
	rootPCB 	= pcbTable; /* First process in the table is root. Keep a permanet pointer to it since it may be useful. */
	idleprocPCB = (pcb*) pcbTable->next; /* Second process in the table is idleproc. 
										 	We need this pointer to handle idling. */
	/* 	Coming straight out of the init(), the idleproc is on the ready queue after creation. 
		I want to remove it. */
	popQueue(&readyQueue, idleprocPCB->pid, IDLE);

	disp_show();
}

/* The dispatcher eternally selects a process to run, allows it to run, grabs its system call arguments and 
	processes them */
void dispatch(){
	initDispatcher();
	
	/* Get first process running */
	pcb* process = next(); /* Should load root from the front of the ready queue */
	
	while(TRUE){
		DELAY(DISP_CYCLE_DELAY);
		
		static int dispatchCycle = 0;
		dispatchCycle++;
		verbose("\n===========================================================\n");
		verbose("Dispatcher commencing cycle %d.\n", dispatchCycle);
		

		if (process == idleprocPCB) {
			/* If we were idling last cycle, check to see if there is a new ready process. */
			idleprocPCB -> state = IDLE;
			process = next(); /* Pick the next ready proc and do NOT put the idleproc on the ready queue. */
		}
		if (process == NULL){
			/* Only execute the idle process if there are NO processes on the ready queue */
			verbose("Dispatcher found no ready process. Idling...");
			process = idleprocPCB;
			idleprocPCB -> state = RUNNING;
		}
		else{
			verbose("Now executing process %d.\n", process -> pid);
		}
		
			
		disp_show();

		/* Execute the process untill a syscall or hardware interrupt occurs */
		system_call request	= contextswitch( process ); 
		/*
		 *      Process is running.............Interupt occurs!
		 */
		
		/* Now control has been returned to the kernel. */
		
		/* Need to get the arguments that were passed to the syscall wrapper, that the user process called. 
			Get the pointer to the va_args that was saved in the process's cpu_context on its stack. */
		uint32*  edxPtr 		= (uint32*) (process->stackPointer + (5 * sizeof(uint32)));
		va_list* apPtr		 	= (va_list*) (*edxPtr);
		
		switch ( request ){
			case ( SYSCREATE ): 
				verbose("Dispatcher processing CREATE from process %d.\n", process->pid);
					
				/* Get the new process' function pointer and stacksize args */
				void (*functionPointer)();
				functionPointer	= (void (*)(void)) va_arg(*apPtr, char* );
				int	stackSize	= va_arg(*apPtr, int );
				char* name		= va_arg(*apPtr, char*);
				
				if(SYSCALL_UNIT_TEST){
					kprintf("Disp(): fptr: %X, stk: %d.\n", functionPointer, stackSize);
				}

				if(SYSCALL_UT_SHOW_STACK){
					printStackPointerArea((uint32*)process->stackPointer, 6, 0);
					kprintf(
					" StackPointer is at addr %X. EDX was stored in %X. Value inside is %X. Now apPtr points to %X.",
						process->stackPointer, *edxPtr, edxPtr, *apPtr, apPtr);
				}
		
				int creationResult = create (functionPointer, stackSize, name); 
				if(creationResult <= process->pid){
					kprintf("SYSCALL ERROR: Creation failed! Generic error, or illegal PID assigned!\n");
					break;
				}
			
				/* 	Access the process at the end of the ready queue (the new created one) 
					so we can fill out the pcb's 'parent_pid' field */
				queueElem * indexElem = readyQueue;
				while (indexElem->next != NULL){
					indexElem = (queueElem*) indexElem->next;
				}
				indexElem->processPointer->parent_pid = process->pid; 
				
				/* Save system call return value in process saved cpu context 'eax' on stack. It will be popa'ed in. */
				process->procEAX = (uint32) creationResult;
				break;
				
			case ( TIMER_INT ):
				verbose("Process %d preempted by timer.\n", process->pid);
				if (process != idleprocPCB){
					ready( process );
					process = next();
				}
				end_of_intr();
				break;
				
			case ( SYSYIELD ):
				verbose("Dispatcher processing YIELD from process %d.\n", process->pid);
				ready( process );
				process = next();
				break;
				
			case ( SYSSTOP ):
				verbose("Dispatcher processing STOP from process %d.\n", process->pid);
				if (cleanup ( process ) == FALSE) ready(process); // If cleanup failed, treat this as a yield.
				process = next();
				break;
				
			case ( SYSGETPID ):
				verbose("Dispatcher processing GETPID from process %d.\n", process->pid);
				/* Save system call return value in process saved cpu context 'eax' on stack. It will be popa'ed in. */
				process->procEAX = (uint32) creationResult;
				if (SYSCALL_UNIT_TEST) kprintf("Disp: GETPID returning pid '%d'.\n", process->procEAX);
				break;
				
			case ( SYSPUTS ):
				verbose("Dispatcher processing PUTS from process %d.\n", process->pid);
				/* Get the string whose pointer was passed through the syscall */
				char* string	= va_arg(*apPtr, char* );
				kprintf("%s", string);
				break;
				
			case ( SYSSEND ):	
				/* Get the destination PID, data buffer address, and data buffer length from the args */
				/*nop*/;
				int toPID		= va_arg(*apPtr, int );
				void* sbuffer	= (void*) va_arg(*apPtr, char* );
				int sbuffer_len	= va_arg(*apPtr, int );
				verbose("Dispatcher processing SEND of %X bytes from proc %d to %d.\n", sbuffer_len, process->pid, toPID);
				/* Send data to the other process. Block untill complete. */
				process->procEAX = send(toPID, process->pid, sbuffer, sbuffer_len);
				break;
				
			case ( SYSRECV ):	
				/* Get the sender PID, data buffer address, and data buffer length from the args */
				/*nop*/;
				int* fromPID		= va_arg(*apPtr, int* );
				void* rbuffer	= (void*) va_arg(*apPtr, char* );
				int rbuffer_len	= va_arg(*apPtr, int );
				verbose("Dispatcher processing RECV of %X bytes from proc %d to %d.\n", rbuffer_len, process->pid, fromPID);
				/* Receive data from some other process. Block untill complete. */
				process->procEAX = recv(fromPID, process->pid, rbuffer, rbuffer_len);
				break;
		}
	}
}

// ==================== UNIT TESTING =====================
void disp_show(){
	if (KERNEL_VERBOSITY){
		if(DISP_SHOW_PCB_TBL)
			printpcbTable();
		if(DISP_SHOW_QUEUES)
			printReadyQueue();
		if (DISP_UT_SHOW_MEM)
			printMemSpace();
		DELAY(DISP_UT_DELAY);
	}
}

/* Print a single pcb entry */
void printPCB(pcb* indexedProc){
	pcb* nextProc = (pcb*) indexedProc->next;
	uint16 nextPID;
	if (nextProc == NULL){
		nextPID = NULL;
	}
	else{
		nextPID = nextProc->pid;	
	} 
	
	kprintf("    [%7d ][%7d ][%7X ][%7d ][",
			indexedProc->pid, indexedProc->parent_pid, 
			indexedProc->stackPointer, nextPID);
	switch ( indexedProc->state ){
		case RUNNING:
			kprintf("    RUN ");
			break;
		case READY:
			kprintf("  READY ");
			break;
		case BLOCKED:
			kprintf("BLOCKED ");
			break;		
		case DEAD:
			kprintf("   DEAD ");
			break;
		case IDLE:
			kprintf("   IDLE ");
			break;
		default:
			break;
	}
	kprintf("][");
	/* For the name, pad the printed string to achieve a fixed width */
	int nameLength 		= strlen(indexedProc->name);
	int paddingSpaces 	= 0;
	while (nameLength <= 10){
		paddingSpaces++;
		kprintf(" ");
		nameLength = strlen(indexedProc->name) + paddingSpaces;
	}
	kprintf("%s ]", indexedProc->name);
	kprintf("\n");
}

/* Print the current state of a process queue */
void printQueue(queueElem* queueStart){
	kprintf("    [    pid ][ parent ][     sp ][   next ][  state ][       name ]\n");
	queueElem * indexedQueueElement = queueStart;
	
	while (indexedQueueElement != NULL) {
		DELAY(SCROLL_DELAY);
		printPCB(indexedQueueElement->processPointer);
		indexedQueueElement = (queueElem*) indexedQueueElement -> next;
	}
}

void printReadyQueue(){
	kprintf("\nReady Queue:\n");
	printQueue(readyQueue);
}

/* Print the complete PCB table. Only non-DEAD processes are shown since cleanup() deletes DEAD process' pcbs. */
void printpcbTable(){
	kprintf("\nAll Processes:\n");
	kprintf("    [    pid ][ parent ][     sp ][   next ][  state ][       name ]\n");
	pcb* indexedProc = pcbTable;
	
	while (indexedProc != NULL) {
		DELAY(SCROLL_DELAY);
		printPCB(indexedProc);
		indexedProc = (pcb*) indexedProc -> next;
	}
}

char* getQueueName(queueElem* queue){
/*	static char stringName[50];
	if (queue == putsQueue)
		sprintf(stringName, "Puts Queue");
	if (queue == sendQueue)
		sprintf(stringName, "Sending Queue");
	if (queue == recvQueue)
		kprintf(stringName, "Recieving Queue");
	if (queue == readyQueue)
		kprintf(stringName, "Ready Queue");
		
	return stringName;*/
}



/* Prints the memory values in an area surrounding a pointer. Assumes that stack pushed values are 32 bits. */
void printStackPointerArea(uint32* stackPointer, int numHigher, int numLower){
	uint32* currentSpot =  stackPointer;
	currentSpot 		-= numLower;
	
	kprintf("\nPrinting stack region around %X.\nAddress:", stackPointer);
	while(currentSpot <= (stackPointer + numHigher)){
		if(currentSpot == stackPointer){
			kprintf("<%8X>", currentSpot);
		}
		else kprintf("[%8X]",currentSpot);
		currentSpot += 1;
	}
	
	currentSpot = stackPointer - numLower;
	kprintf("\n  Value:");
	while(currentSpot <= (stackPointer + numHigher)){
		kprintf("[%8X]",*currentSpot);
		currentSpot += 1;
	}
	kprintf("\n");
}





